package com.partsoft.dits.mq;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.pool.KeyedObjectPool;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.ibm.mq.MQException;
import com.partsoft.dits.AbstractConfigurableDescriptorManager;
import com.partsoft.dits.DescriptorManager;
import com.partsoft.dits.DescriptorRegistry;
import com.partsoft.dits.log.Log;

/**
 * 上下文可配置的MQ连接池管理器
 */
public class ContextConfigurableMQPoolManager extends
		AbstractConfigurableDescriptorManager<MQPoolDescriptor> implements
		DescriptorRegistry<MQPoolDescriptor>,
		DescriptorManager<MQPoolDescriptor>, 
		ApplicationContextAware, 
		ApplicationListener<ApplicationEvent>, 
		MQPoolProvider, 
		MQPoolManager {

	private Map<String, KeyedObjectPool> qmPoolMap = Collections.synchronizedMap(new HashMap<String, KeyedObjectPool>(5)); 
	
	public Class<MQPoolDescriptor> getSupportClass() {
		return MQPoolDescriptor.class;
	}
	
	protected KeyedObjectPool doAddFactoryToPool(String id, MQConnectFactory qmFactory, int maxActive, int maxIdle,  int minIdle, 	boolean testOnBorrow, boolean testOnReturn, boolean testWhileIdle) {
		MQQMObjectPool mto = null;
		synchronized (this) {
			mto = new MQQMObjectPool();
			mto.setFactory((new MQQMPoolFactory(qmFactory)));
			qmPoolMap.put(id, mto);
		}
		Log.info(String.format("成功创建MQ连接池(%s)。", id));
		return mto; 
	}

	@Override
	protected void beforeRemoveDescriptor(MQPoolDescriptor descriptor) {
		synchronized (this) {
			KeyedObjectPool pool = qmPoolMap.remove(descriptor.getId());
			if (pool != null) try {
				pool.close();
			} catch (Throwable e) {
				Log.error(String.format("关闭MQ连接池(%s)时发生错误(%s)", descriptor.toString(), e.getMessage()), e);
			}
		}
		Log.info(String.format("关闭MQ连接池(%s)成功。", descriptor.getId()));
	}

	@Override
	protected void postRemoveDescriptor(MQPoolDescriptor descriptor) {
		
	}

	@Override
	protected void beforeLoadConfiguredDescriptor(MQPoolDescriptor descriptor) {
		MQConnectFactory mqFactory = createMQConnectFactory(descriptor);
		doAddFactoryToPool(descriptor.getId(), mqFactory, descriptor.getMaxActive(), 
				descriptor.getMaxIdle(), descriptor.getMinIdle(), descriptor.isTestOnBorrow(), 
				descriptor.isTestOnReturn(), descriptor.isTestWhileIdle());
	}
	
	protected MQConnectFactory createMQConnectFactory(MQPoolDescriptor descriptor) {
		Assert.notNull(descriptor);
		AbstractMQConnectFactory factory = null;
		
		if (descriptor.isRemoteBind()) {
			RemoteBindMQConnectFactory rf = new RemoteBindMQConnectFactory();
			rf.setHostName(descriptor.getHostname());
			rf.setPort(descriptor.getPort());
			rf.setChannelName(descriptor.getChannelName());
			rf.setUserId(descriptor.getUserId());
			rf.setPassword(descriptor.getPassword());
			factory = rf;
		} else {
			factory = new LocalBindMQConnectFactory();
		}
		factory.setDefaultQMName(descriptor.getDefaultQMName());
		return factory;
	}
	
	/**
	 * 创建并校验MQ连接
	 */
	protected MQConnectFactory createMQConnectFactoryAndValidateIt(MQPoolDescriptor descriptor) {
		MQConnectFactory factory = createMQConnectFactory(descriptor);
		try {
			factory.getDefaultQueueManager();
		} catch (MQException e) {
			throw new IllegalArgumentException(String.format("MQ连接错误:%s", e.getMessage()), e);
		}
		return factory;
	}
	
	@Override
	protected void setupDescriptorIdentifier(MQPoolDescriptor descriptor, String id) {
		descriptor.setId(id);
	}

	@Override
	protected Properties beforeAddDescriptor(MQPoolDescriptor descriptor) {
		synchronized (this) {
			MQConnectFactory mqFactory = createMQConnectFactoryAndValidateIt(descriptor);
			//创建连接池
			doAddFactoryToPool(descriptor.getId(), mqFactory, descriptor.getMaxActive(), 
					descriptor.getMaxIdle(), descriptor.getMinIdle(), descriptor.isTestOnBorrow(), 
					descriptor.isTestOnReturn(), descriptor.isTestWhileIdle());
			
			Properties props = new Properties();
			if (descriptor.isRemoteBind()) {
				props.setProperty("hostname", descriptor.getHostname());
				props.setProperty("port", Integer.toString(descriptor.getPort()));
				props.setProperty("channelName", descriptor.getChannelName());
				if (StringUtils.hasText(descriptor.getUserId()))
					props.setProperty("userId", descriptor.getUserId());
				if (StringUtils.hasText(descriptor.getPassword()))
					props.setProperty("password", descriptor.getPassword());
			}
			props.setProperty("defaultQMName", descriptor.getDefaultQMName());
			
			props.setProperty("maxActive", Integer.toString(descriptor.getMaxActive()));
			props.setProperty("maxIdle", Integer.toString(descriptor.getMaxIdle()));
			props.setProperty("minIdle", Integer.toString(descriptor.getMinIdle()));
			props.setProperty("testOnBorrow", Boolean.toString(descriptor.isTestOnBorrow()));
			props.setProperty("testOnReturn", Boolean.toString(descriptor.isTestOnReturn()));
			props.setProperty("testWhileIdle", Boolean.toString(descriptor.isTestWhileIdle()));
			props.setProperty("title", descriptor.getTitle());
			props.setProperty("description", descriptor.getDescription());
			props.setProperty("ordinal", Integer.valueOf(descriptor.getOrdinal()).toString());

			return props;
		}
	}

	@Override
	protected void validateDescriptor(MQPoolDescriptor descriptor) {
		Assert.notNull(descriptor.getDefaultQMName(), "必须设置缺省队列管理器参数。");
		if (descriptor.isRemoteBind())
			Assert.hasText(descriptor.getChannelName(), "远程连接必须设置通道名称。");
		
		if (StringUtils.hasText(descriptor.getUserId())) 
			Assert.hasText(descriptor.getPassword(), "设置了用户名必须设置密码。");
	}
	
	protected String resolveDescriptorIdentifier(String hostName, int port, String channelName, String qmName) {
		StringBuffer buffer = new StringBuffer();
		if (StringUtils.hasText(hostName)) {
			buffer.append("mqremote.").append(hostName).append(".").append(port).append(".").append(channelName).append(".").append(qmName);
		} else {
			buffer.append("mqlocal.").append(qmName);
		}
		return buffer.toString();
	}

	@Override
	protected String resolveDescriptorIdentifier(MQPoolDescriptor descriptor) {
		return StringUtils.hasText(descriptor.getId()) ? descriptor.getId() : 
			resolveDescriptorIdentifier(descriptor.getHostname(), 
					descriptor.getPort(), descriptor.getChannelName(), 
					descriptor.getDefaultQMName());
	}

	//MQPoolProvider Implements
	public KeyedObjectPool getMQPool(MQPoolDescriptor desc) throws RuntimeException {
		return getMQPool(resolveDescriptorIdentifier(desc)); 
	}

	public KeyedObjectPool getMQPool(String id) throws RuntimeException {
		if (!hasMQPool(id)) throw new IllegalArgumentException(String.format("找不到MQ池(%s)", id));
		return qmPoolMap.get(id);
	}

	public boolean hasMQPool(MQPoolDescriptor desc) {
		return hasDescriptor(desc);
	}

	public boolean hasMQPool(String id) {
		return hasDescriptor(id);
	}

	public KeyedObjectPool getMQPool(String hostName, int port,
			String channelName, String qmName) throws RuntimeException {
		return getMQPool(resolveDescriptorIdentifier(hostName, port, channelName, qmName));
	}

	public boolean hasMQPool(String hostName, int port, String channelName, String qmName) {
		return hasDescriptor(resolveDescriptorIdentifier(hostName, port, channelName, qmName));
	}
	
	//MQPoolManager Implements
	public MQQMObjectPool addMQPool(MQPoolDescriptor descriptor) {
		return (MQQMObjectPool) getMQPool(addDescriptor(descriptor));
	}

	public MQQMObjectPool addMQPool(String hostName, int port,
			String channelName, String qmName) {
		MQPoolDescriptor descriptor = new MQPoolDescriptor();
		descriptor.setHostname(hostName);
		descriptor.setPort(port);
		descriptor.setChannelName(channelName);
		descriptor.setDefaultQMName(qmName);
		return addMQPool(descriptor);
	}

	public MQQMObjectPool addMQPool(String qmName) {
		return addMQPool(null, 1414, null, qmName);
	}

	public void removeMQPool(MQPoolDescriptor descriptor) {
		removeDescriptor(descriptor);
	}

	public void removeMQPool(String qmName) {
		removeMQPool(null, 1414, null, qmName);
	}

	public void removeMQPool(String hostName, int port,
			String channelName, String qmName) {
		MQPoolDescriptor descriptor = new MQPoolDescriptor();
		descriptor.setHostname(hostName);
		descriptor.setPort(port);
		descriptor.setChannelName(channelName);
		descriptor.setDefaultQMName(qmName);
		removeMQPool(descriptor);
	}
}
